package com.naiterui.common.repository.page;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.ToStringBuilder;

/**
 * 与具体ORM实现无关的分页参数及查询结果封装.
 * <P>File name : Page.java </P>
 * <P>Author : zhouyanxin </P>
 * <P>Date : 2014-9-18 </P>
 */
@SuppressWarnings("unused")
public class Page<T> implements Serializable {

    /**
     * 字段或域定义：<code>serialVersionUID</code>
     */
    private static final long serialVersionUID = -7618231474413524175L;

    // 公共变量
    public static final String ASC = "asc";
    public static final String DESC = "desc";
    public static final int MIN_PAGESIZE = 20;
    public static final int MAX_PAGESIZE = 5000;

    /**
     * 页码
     */
    protected int pageNo = 1;

    /**
     * 每页记录数
     */
    protected int pageSize = MIN_PAGESIZE;

    /**
     * 请求第一页时的总记录数
     */
    protected int offset;

    /**
     * 结果集
     */
    protected List<T> result;

    /**
     * 总记录数
     */
    protected long totalCount;

    /**
     * 偏移量(根据pageNo和pageSize计算当前页第一条记录在总结果集中的位置,序号从0开始)
     */
    protected int first;

    /**
     * 总页数
     */
    protected long totalPages;

    /**
     * 是否还有下一页
     */
    protected boolean hasNext;

    /**
     * 下一页页码
     */
    protected int nextPage;

    /**
     * 是否有上一页
     */
    protected boolean hasPre;

    /**
     * 上一页的页码
     */
    protected int prePage;

    /**
     * 排序字段
     */
    protected String orderBy;

    /**
     * 正序、倒序
     */
    protected String order;

    /**
     * 是否排序
     */
    protected boolean orderBySetted;

    /**
     * 是否使用无惨构造, 如果使用无惨构造,那么需要手动计算上一页下一页等字段
     */
    private boolean userNoArgConstructor;

    /********************** constructs **********************/

    /**
     * 构造函数
     */
    public Page() {
        this.userNoArgConstructor = true;
    }

    /**
     * 构造函数
     */
    private Page(int pageNo, int pageSize) {
        this.pageNo = pageNo;
        this.pageSize = pageSize;
        this.result = new ArrayList<>();
    }

    /**
     * 构造函数
     */
    private Page(int pageNo, int pageSize, int offset) {
        this(pageNo, pageSize);
        this.offset = offset;
    }

    /**
     * 构造函数
     */
    private Page(int pageNo, int pageSize, List<T> result, long totalCount) {
        this(pageNo, pageSize, result, totalCount, 0);
    }

    /**
     * 构造函数
     */
    private Page(int pageNo, int pageSize, List<T> result, long totalCount, int offset) {
        this.pageNo = pageNo;
        this.pageSize = pageSize;
        this.result = result;
        this.totalCount = totalCount;
        this.offset = offset;
        this.first = (this.pageNo - 1) * this.pageSize;
        ////////////////计算总页数////////////////////////
        if (this.offset > 0 && this.offset > this.totalCount) {
            this.totalCount = this.offset;
        }
        long count = this.totalCount / this.pageSize;
        if (this.totalCount % this.pageSize > 0) {
            count++;
        }
        this.totalPages = count;
        ////////////////计算总页数////////////////////////
        this.hasNext = (this.pageNo + 1) <= this.totalPages;
        this.nextPage = this.hasNext ? this.pageNo + 1 : this.pageNo;
        this.hasPre = this.pageNo != 1;
        this.prePage = this.hasPre ? this.pageNo - 1 : this.pageNo;
        this.order = "";
        this.orderBy = "";
        this.orderBySetted = StringUtils.isNotBlank(this.orderBy);
    }

    /********************** public methods **********************/

    /**
     * 将Page<T>的分页结果集通过Convertor接口转换为Page<U>
     * Page.map()
     *
     * @param converter
     *
     * @return
     *
     * @Author zhouyanxin
     * @Date 2018年5月20日
     * @since 1.0.0
     */
    <U> Page<U> map(Function<T, U> converter) {
        Stream<T> stream = this.result.stream();
        List<U> list = stream.map(converter).collect(Collectors.toList());
        return new Page<>(this.pageNo, this.pageSize, list, this.totalCount, this.offset);
    }

    /********************** static methods **********************/

    /**
     * 构造空结果集的Page对象
     * Page.buildEmptyPage()
     *
     * @param pageNo
     * @param pageSize
     * @param offset
     *
     * @return
     *
     * @Author zhouyanxin
     * @Date 2018年6月7日
     * @since 1.0.0
     */
    public static <T> Page<T> buildEmptyPage(int pageNo, int pageSize) {
        return new Page<>(pageNo, pageSize);
    }

    /**
     * 构造空结果集的Page对象
     * Page.buildEmptyPage()
     *
     * @param pageNo
     * @param pageSize
     * @param offset
     *
     * @return
     *
     * @Author zhouyanxin
     * @Date 2018年6月7日
     * @since 1.0.0
     */
    public static <T> Page<T> buildEmptyPage(int pageNo, int pageSize, int offset) {
        return new Page<>(pageNo, pageSize, offset);
    }

    /**
     * 构造分页信息返回对象
     * Page.buildPage()
     *
     * @param pageNo
     * @param pageSize
     * @param results
     * @param totalCount
     *
     * @return
     *
     * @Author zhouyanxin
     * @Date 2018年6月7日
     * @since 1.0.0
     */
    public static <T> Page<T> buildPage(int pageNo, int pageSize, List<T> results, Long totalCount) {
        return new Page<>(pageNo, pageSize, results, totalCount);
    }

    /**
     * 构造分页信息返回对象
     * Page.buildPage()
     *
     * @param pageNo
     * @param pageSize
     * @param results
     * @param totalCount
     * @param offset
     *
     * @return
     *
     * @Author zhouyanxin
     * @Date 2018年6月7日
     * @since 1.0.0
     */
    public static <T> Page<T> buildPage(int pageNo, int pageSize, List<T> results, Long totalCount, int offset) {
        return new Page<>(pageNo, pageSize, results, totalCount, offset);
    }

    /**
     * 根据指定的pageNo和pageSize，计算起始的offset
     * Page.getStartOfPage()
     *
     * @param pageNo
     * @param pageSize
     *
     * @return
     *
     * @Author zhouyanxin
     * @Date 2018年6月7日
     * @since 1.0.0
     */
    public static int getStartOfPage(int pageNo, int pageSize) {
        return (pageNo > 0 && pageSize > 0) ? (pageNo - 1) * pageSize : 0;
    }

    /********************** Getter **********************/

    public int getPageNo() {
        return this.pageNo;
    }

    public int getPageSize() {
        return this.pageSize;
    }

    public int getFirst() {
        return this.userNoArgConstructor ? (this.pageNo - 1) * this.pageSize : this.first;
    }

    public String getOrderBy() {
        return this.orderBy;
    }

    public boolean isOrderBySetted() {
        return this.orderBySetted;
    }

    public String getOrder() {
        return this.order;
    }

    public List<T> getResult() {
        return this.result;
    }

    public long getTotalCount() {
        return this.totalCount;
    }

    public long getTotalPages() {
        if (this.userNoArgConstructor) {
            if (this.totalCount < 0) {
                return -1;
            }

            long count = this.totalCount / this.pageSize;
            if (this.totalCount % this.pageSize > 0) {
                count++;
            }
            return count;
        }
        return this.totalPages;
    }

    public boolean isHasNext() {
        return this.userNoArgConstructor ? this.pageNo + 1 <= this.getTotalPages() : this.hasNext;
    }

    public int getNextPage() {
        return this.userNoArgConstructor ? this.isHasNext() ? this.pageNo + 1 : this.pageNo : this.nextPage;
    }

    public boolean isHasPre() {
        return this.userNoArgConstructor ? this.pageNo - 1 >= 1 : this.hasPre;
    }

    public int getPrePage() {
        if (this.userNoArgConstructor) {
            return this.isHasPre() ? this.pageNo - 1 : this.pageNo;
        }
        return this.prePage;
    }

    public int getOffset() {
        return this.offset;
    }

    /********************** Setter **********************/

    public void setOffset(int offset) {
        this.offset = offset;
    }

    public void setPageNo(final int pageNo) {
        this.pageNo = pageNo;
        if (pageNo < 1) {
            this.pageNo = 1;
        }
    }

    public void setPageSize(final int pageSize) {
        this.pageSize = pageSize;
        if (pageSize > MAX_PAGESIZE) {
            this.pageSize = MAX_PAGESIZE;
        }
    }

    public void setResult(List<T> result) {
        this.result = result;
    }

    public void setTotalCount(long totalCount) {
        this.totalCount = totalCount;
    }

    public void setFirst(int first) {
        this.first = first;
    }

    public void setTotalPages(long totalPages) {
        this.totalPages = totalPages;
    }

    public void setHasNext(boolean hasNext) {
        this.hasNext = hasNext;
    }

    public void setNextPage(int nextPage) {
        this.nextPage = nextPage;
    }

    public void setHasPre(boolean hasPre) {
        this.hasPre = hasPre;
    }

    public void setPrePage(int prePage) {
        this.prePage = prePage;
    }

    public void setOrderBy(String orderBy) {
        this.orderBy = orderBy;
    }

    public void setOrder(String order) {
        this.order = order;
    }

    public void setOrderBySetted(boolean orderBySetted) {
        this.orderBySetted = orderBySetted;
    }

    @Override
    public String toString() {
        return ToStringBuilder.reflectionToString(this);
    }
}
